
__author__    = "Jussi Toivola <jussi@redinnovation.com>"
__copyright__ = "2008 Red Innovation Ltd."
__license__   = "AGPL"
__docformat__ = "epytext"

import _ipc
import time
import e32

# import pylogman as l
# import sys
# sys.stdout.write = l.Log

#: Maximum size for buffer to be written
BUFFER_MAX_SIZE = _ipc.BUFFER_MAX_SIZE

class NotifierWrapper:
    "Wrapper for callback notifier to catch exceptions."
    
    def __init__( self, notifier_setter ):
        #: The user supplied callback for notifier.
        self.Callback = None
        
        #: Setter for data or space notifier
        self.NotifierSetter = notifier_setter
        
    def Notifier( self ):
        """
        Wrapper for user given notifier.
        
        Catches exceptions from the notifier callback
        to avoid problems with the c-extension.
        """
        
        result = False
        try:
            result = self.Callback()
        except Exception, msg:
            import traceback
            traceback.print_exc()
            result = False # Stops notifier automatically on exception.

        return result
        
    def set_notifier( self, callback ):
        """
        Wrapper for setting the notifier callback.
        """
        
        # Update callback called by Notifier method
        self.Callback = callback

        if callback is None:
            # No need for wrapper here.
            self.NotifierSetter( None )
        else:
            # Give our exception-catching wrapper to c-extension.
            self.NotifierSetter( self.Notifier )

class MsgQueue:
    """
    Wrapper for RMsgQueue.
    Maximum size for a message to be written is 128 bytes. This class splits
    longer messages into suitable chunks.
    """
    def __init__(self, queuename ):
        self._queue = _ipc.MsgQueue( queuename )

        inherited = [
                        "open",
                        #"close",
                        "create",
                        #"set_data_notifier",
                        #"set_space_notifier"
                    ]

        for x in inherited:
            setattr( self, x, getattr( self._queue, x ) )

        self.init_notifiers()
        
    def init_notifiers(self):
        """
        Initializes callback notifier.
        Must be called if trying to use notifiers after calling close().
        """
        # Wrapper for data notifications.
        self.DataNotifier      = NotifierWrapper( self._queue.set_data_notifier )
        self.set_data_notifier = self.DataNotifier.set_notifier

        # Wrapper for space notifications.
        self.SpaceNotifier = NotifierWrapper( self._queue.set_space_notifier )
        self.set_space_notifier = self.SpaceNotifier.set_notifier

    def close( self ):
        """Close queue and break reference cycles"""
        del self.DataNotifier.NotifierSetter
        del self.DataNotifier.Callback

        del self.SpaceNotifier.NotifierSetter
        del self.SpaceNotifier.Callback
        
        self._queue.close()

    def recv( self, chunks = 1):
        """
        @param chunks: How many chunks of data to receive. Maximum size of a
                       chunk is 128 bytes.
                       If zero(0) or less, gets data until empty string is received.
        @return: Data from queue.
        @rtype:  str
        """
        
        result = ""
        # Get all data from queue.
        if chunks <= 0:
            chunk = None
            while chunk != "":
                chunk = self._queue.recv()
                result += chunk
        else:
            for x in xrange( chunks ):
                result += self._queue.recv()
                
        return result

    def write( self, data, timeout = None, wait = 0.1 ):
        """
        Write data to queue. If all data does not fit into the queue,
        the remaining data is returned.
        @param data: Data to send
        @param timeout: Timeout in seconds.
                        If all data is not written within this time,
                        remaining data is returned.
        @param wait: Seconds to wait before trying to send again when using timeout.
        @return: Remaining data. Emptry string, if all data was added to queue.
        """
        # Ensure it is string and not unicode.
        data = str( data )
        curpos = 0
        newpos = 0
        length = len( data )
        
        # Timeout stuff
        started = 0
        end     = 0
        if timeout is not None:
            end     = time.time()
            end += timeout
        
        # Start sending loop
        while curpos < length:
            newpos = min( curpos + BUFFER_MAX_SIZE, length )
            chunk  = data[curpos:newpos]
            try:
                self._queue.write( chunk )
            except SymbianError, msg:
                # Check timeout
                if timeout is not None:
                    if time.time() < end:
                        # Sleep for a while before trying again.
                        e32.ao_sleep( wait )
                        continue
                    else:
                        break
                
                if msg.args[1] == 'KErrOverflow':
                    break # Return remaining data.
                # Else
                raise 
                
            curpos = newpos

        return data[curpos:]
        
        